import java.io.*;
import java.util.HashMap;

class Util {
    public static void BugOn(boolean condition) {
        if(condition) {
            String s = "Quack! ";
            s += "(" + Thread.currentThread().getName() + ") ";
            s += "" + new Throwable().getStackTrace()[1].getFileName() + " ";
            s += "" + new Throwable().getStackTrace()[1].getLineNumber() + " ";
            System.out.println(s);
            while(true);
        }
    }
    public static Object ByteArray2Object(byte[] b) {
        Object ret = null;
        ByteArrayInputStream bis = new ByteArrayInputStream(b);
        ObjectInputStream ois;

        if(b == null || b.length == 0) return ret;

        try {
            ois = new ObjectInputStream(bis);
            ret = ois.readObject();
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
        return ret;
    }
    public static byte[] Object2ByteArray(Serializable obj) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(baos);
            oos.writeObject(obj);
        }
        catch(IOException e) {
        }
        byte[] ret = baos.toByteArray();
        return ret;
    }
    public static byte[] IntArray2ByteArray(int[] v, int length) {
        byte[] ret = new byte[length];

        for(int i = 0; i < length; ++i) {
            int temp = v[i / 4] >> (3 - (i % 4)) * 8;
            temp &= 0xFF;
            ret[i] = (byte) temp;
        }
        return ret;

    }

    public static int[] ByteArray2IntArray(byte[] v) {
        if(v.length == 0) return null;
        int size = v.length / 4;
        if(v.length % 4 != 0) size++;

        int[] ret  = new int[size]; 
        for(int i = 0; i < ret.length; ++i) {
            ret[i] = 0;
        }


        for(int i = 0; i < v.length; ++i) {
            ret[i / 4] <<= 8;
            ret[i / 4] += (0xFF & v[i]);
        }
        if(v.length % 4 != 0) {
            ret[ret.length - 1] <<= 8 * (3 - (v.length % 4));
        }
        return ret;
    }
}

class FusionEntry {
    int numFaults;
    int numObjs;
    int[] localCode;
    HashMap<Integer, Integer> mapObjSize;
    //Fusion fusion;
    FusionAdd fusion;
    int idx;
  
    public FusionEntry(int numObjs, int numFaults, int idx) {
        this.numFaults = numFaults;
        this.numObjs = numObjs;
        this.idx = idx;
        
        fusion = new FusionAdd(numObjs, numFaults);
        mapObjSize = new HashMap<Integer, Integer>();
    }
    public int[] getCode() {
        return localCode;
    }
    public Object[] getRecoveredData(int[] objIdx, int[][] code, HashMap<Integer, Serializable> mapPrimariesObj) {
        Object[] ret = new Object[objIdx.length];
        int[][] iaRet = new int[objIdx.length][];
   
        int[] erasures = new int[numFaults + 1];
        int[][] primariesData = new int[numObjs][];
        
        Util.BugOn(idx > code.length - 1);
        code[idx] = localCode;
        int[][] rsCode = null;
        if(code != null && code[0] != null) {
            rsCode = new int[code[0].length][code.length]; 
            for(int i = 0; i < code[0].length; ++i) {
                for(int j = 0; j < code.length; ++j) {
                    if(i < code[j].length) {
                        rsCode[i][j] = code[j][i];
                    }
                }
            }
        }

        int numErasures = 0;
        for(int i = 0; i < numObjs; ++i) {
            if(!mapPrimariesObj.containsKey(i)) {
                erasures[numErasures++] = i;
            }
            else {
                byte[] baObj = Util.Object2ByteArray(mapPrimariesObj.get(i));
                primariesData[i] = Util.ByteArray2IntArray(baObj);
            }
            if(numErasures > numFaults) {
                return ret;
            }
        }
        for(int i = numErasures; i < numFaults + 1; ++i) {
            erasures[i] = -1;
        }

        for(int i = 0; i < iaRet.length; ++i) {
            System.out.println("objIdx: " + objIdx[i]);
            int iaLength = mapObjSize.get(objIdx[i]) / 4;
            if(mapObjSize.get(objIdx[i]) % 4 != 0) {
                iaLength++;
            }
            iaRet[i] = new int[iaLength];
        }

        int[] dataword = new int[numObjs];
        

        for(int i = 0; i < rsCode.length; ++i) {
            for(int j = 0; j < numObjs; ++j) {
                if(primariesData[j] == null) {
                    dataword[j] = 0;
                }
                else if(i < primariesData[j].length) {
                    dataword[j] = primariesData[j][i];
                    System.out.println(rsCode.length + "\t" + primariesData[j].length + "\t" + i);
                    System.out.println("j: " + j + " data: " + dataword[j]);
                }
            }
            try {
                int[] data = fusion.getRecoveredData(rsCode[i], dataword, erasures);
                for(int k = 0; k < objIdx.length; ++k) {
                    if(iaRet[k].length > i) {
                        iaRet[k][i] = data[objIdx[k]];
                    }
                }
            }
            catch(InterfaceException e) {
            }
        }
        for(int i = 0; i < objIdx.length; ++i) {
            byte[] baRet = Util.IntArray2ByteArray(iaRet[i], mapObjSize.get(objIdx[i]));
            System.out.println("byte array length: " + baRet.length);
            ret[i] = Util.ByteArray2Object(baRet);
            if(ret[i] == null) System.out.println("null obj");
        }
        return ret;
    }
    public void updateCodes(Serializable oldElement, Serializable newElement, int objIdx) {
        byte[] baOldElement = null;
        byte[] baNewElement = null;
        int[] iaOldElement = null;
        int[] iaNewElement = null;
        int maxLength = (localCode == null) ? 0 : localCode.length;
        
        if(oldElement == null && newElement == null) return; 

        if(oldElement != null) {
            baOldElement = Util.Object2ByteArray(oldElement);
            iaOldElement = Util.ByteArray2IntArray(baOldElement);
            maxLength = (maxLength < iaOldElement.length) ? iaOldElement.length : maxLength;
        }
        if(newElement != null) {
            baNewElement = Util.Object2ByteArray(newElement);
            iaNewElement = Util.ByteArray2IntArray(baNewElement);
            mapObjSize.put(objIdx, baNewElement.length);
            maxLength = (maxLength < iaNewElement.length) ? iaNewElement.length : maxLength;
        }
        else {
            mapObjSize.put(objIdx, 0);
        }
         
        if(localCode == null) {
            localCode = new int[maxLength];
            for(int i = 0; i < maxLength; ++i) {
                localCode[i] = 0; 
            }
        }
        if(localCode.length < maxLength) {
            int[] newCode = new int[maxLength];
            System.arraycopy(localCode, 0, newCode, 0, localCode.length);
            localCode = newCode;
        }
        for(int i = 0; i < maxLength; ++i) {
            int newVal = 0;
            int oldVal = 0;
            if(iaOldElement != null && i < iaOldElement.length) {
                oldVal = iaOldElement[i];
            }
            if(iaNewElement != null && i < iaNewElement.length) {
                newVal = iaNewElement[i];
            }
            try {
                localCode[i] = fusion.getUpdatedCode(localCode[i], idx, oldVal, newVal, objIdx);
            }
            catch(InterfaceException e) {
            }
        }
    }
    //public void updateCodeWords(Serializable oldElement, Serializable newElement, int idx) {
    //    byte[] baOldElement = null;
    //    byte[] baNewElement = null;
    //    int[] iaOldElement = null;
    //    int[] iaNewElement = null;
    //    int maxLength = (code == null) ? 0 : code.length;
    //    
    //    if(oldElement == null && newElement == null) return; 

    //    if(oldElement != null) {
    //        baOldElement = Util.Object2ByteArray(oldElement);
    //        iaOldElement = Util.ByteArray2IntArray(baOldElement);
    //        maxLength = (maxLength < iaOldElement.length) ? iaOldElement.length : maxLength;
    //    }
    //    if(newElement != null) {
    //        baNewElement = Util.Object2ByteArray(newElement);
    //        iaNewElement = Util.ByteArray2IntArray(baNewElement);
    //        mapObjSize.put(idx, baNewElement.length);
    //        maxLength = (maxLength < iaNewElement.length) ? iaNewElement.length : maxLength;
    //    }
    //     
    //    if(code == null) {
    //        code = new int[maxLength][numFaults];
    //        for(int i = 0; i < maxLength; ++i) {
    //            for(int j = 0; j < numFaults; ++j) {
    //                code[i][j] = 0; 
    //            }
    //        }
    //    }
    //    if(code.length < maxLength) {
    //        int[][] newCode = new int[maxLength][numFaults];
    //        System.arraycopy(code, 0, newCode, 0, code.length);
    //        code = newCode;
    //    }
    //    for(int i = 0; i < maxLength; ++i) {
    //        int newVal = 0;
    //        int oldVal = 0;
    //        if(iaOldElement != null && i < iaOldElement.length) {
    //            oldVal = iaOldElement[i];
    //        }
    //        if(iaNewElement != null && i < iaNewElement.length) {
    //            newVal = iaNewElement[i];
    //        }
    //        try {
    //            code[i] = fusion.getUpdatedCodeWords(code[i], oldVal, newVal, idx);
    //        }
    //        catch(InterfaceException e) {
    //        }
    //    }
    //}
}

class Foo implements Serializable {
    int a;
    int b;
    int c;
    public Foo(int a, int b, int c) {
        this.a = a;
        this.b = b;
        this.c = c;
    }
    public Foo() {
        a = 1;
        b = 2;
        c = 3;
    }
    public void printInfo() {
        System.out.println("a: " + a + ", b: " + b + ", c: " + c);
    }
}

class Bar implements Serializable {
    int a;
    int b;
    byte c;

    public Bar(int a, int b) {
        this.a = a;
        this.b = b;
        c = 100;
    }
    public Bar() {
        a = 3;
        b = 2;
        c = 1;
    }
    public void printInfo() {
        System.out.println("a: " + a + ", b: " + b + ", c: " + c);
    }
}



public class ObjFusion {
    public static void main(String[] args) {
        Foo foo = new Foo();
        Bar bar = new Bar();
        Bar bar1 = new Bar(10, 11);
        Bar bar2 = new Bar(100, 110);
        Foo foo2 = new Foo(10, 11, 12);
        Foo foo3 = new Foo(100, 110, 120);


        byte[] baFoo = Util.Object2ByteArray(foo);
        byte[] baFoo2 = Util.Object2ByteArray(foo2);
        byte[] baBar = Util.Object2ByteArray(bar);

        int[] iaFoo = Util.ByteArray2IntArray(baFoo);
        int[] iaFoo2 = Util.ByteArray2IntArray(baFoo2);
        int[] iaBar = Util.ByteArray2IntArray(baBar);


        // fusion

        //code = Fusion(code, iaFoo, iaBar, 1);

        byte[] baFooRet = Util.IntArray2ByteArray(iaFoo, baFoo.length);
        byte[] baFooRet2 = Util.IntArray2ByteArray(iaFoo2, baFoo2.length);
        byte[] baBarRet = Util.IntArray2ByteArray(iaBar, baBar.length);


        foo = null;
        foo = (Foo)Util.ByteArray2Object(baFooRet);
        foo2 = null;
        foo2 = (Foo)Util.ByteArray2Object(baFooRet2);
        bar = null;
        bar = (Bar)Util.ByteArray2Object(baBarRet);

        if(foo != null)
            foo.printInfo();
        if(foo2 != null)
            foo2.printInfo();
        if(bar != null) 
            bar.printInfo();
        

        FusionEntry f1 = new FusionEntry(3, 1, 0);
        //FusionEntry f2 = new FusionEntry(3, 2, 1);
        
        f1.updateCodes(null, foo, 0);
        f1.updateCodes(null, foo2, 1);
        f1.updateCodes(null, bar, 2);
        f1.updateCodes(bar, foo3, 2);
        f1.updateCodes(foo3, bar, 2);
 //       f1.updateCodes(foo3, null, 2);


        //f2.updateCodes(null, foo, 0);
        //f2.updateCodes(null, foo2, 1);
        //f2.updateCodes(null, bar, 2);
        //f2.updateCodes(bar, foo3, 2);
        //f2.updateCodes(foo3, null, 2);
        
        foo.printInfo();
        foo2.printInfo();
        bar.printInfo();


        int[] idx = new int[3];
        idx[0] = 0; 
        idx[1] = 1; 
        idx[2] = 2; 

        HashMap<Integer, Serializable> mapPrimariesObj = new HashMap<Integer, Serializable>();
        mapPrimariesObj.put(1, foo2);
        mapPrimariesObj.put(2, bar);
       
        
        int[][] code = new int[1][];
        code[0] = null;
        //code[1] = f2.getCode();



        Object[] objs = f1.getRecoveredData(idx, code, mapPrimariesObj);
        foo = (Foo) objs[0];
        foo2 = (Foo) objs[1];
        bar = (Bar) objs[2];
//        foo3 = (Foo) objs[2];
        foo.printInfo();
        foo2.printInfo();
        bar.printInfo();
 //       foo3.printInfo();
    }
}
